//
//  DWLogManager.m
//  OCAPPDemo
//
//  Created by OS on 2022/1/10.
//

#import "DWLogManager.h"
#import "FCFileManager.h"
#import "DWLogFileForrmatter.h"

#import <UIKit/UIKit.h>
#include <libkern/OSAtomic.h>
#include <execinfo.h>

BOOL const openLog = YES;

NSString * const UncaughtExceptionHandlerSignalExceptionName = @"UncaughtExceptionHandlerSignalExceptionName";
NSString * const UncaughtExceptionHandlerSignalKey = @"UncaughtExceptionHandlerSignalKey";
NSString * const UncaughtExceptionHandlerAddressesKey = @"UncaughtExceptionHandlerAddressesKey";
NSString * const UncaughtExceptionHandlerFileKey = @"UncaughtExceptionHandlerFileKey";

// 记录之前的Crash回调函数（如果有的话）
static NSUncaughtExceptionHandler *previousUncaughtExceptionHandler = NULL;

@interface DWLogManager()

//@property (nonatomic, strong) NSUncaughtExceptionHandler *prevHandler;

@end

@implementation DWLogManager

- (void)exple {
    
}

+ (instancetype)sharedManager {
    static id instance;
    
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        instance = [[self alloc] init];
    });
    
    return instance;
}

- (void)startCrash {
    
    if (!openLog) {
        return;
    }
    
    // OC NSException
    [self registerUncaughtExceptionHandler];
    // Swift Signal
    registerSignalHandler();
    
}


//异常处理方法
- (void)handleException:(NSException *)exception {
    
    NSDictionary *userInfo = exception.userInfo;
    NSArray *callStackSymbols = [userInfo objectForKey:UncaughtExceptionHandlerAddressesKey];
    // 保存Crash日志到沙盒cache目录
    [[DWLogManager sharedManager] writeCrashLog:exception callStackSymbols:callStackSymbols];

}

void registerSignalHandler(void) {
    
    signal(SIGSEGV, handleSignalException);
    signal(SIGFPE, handleSignalException);
    signal(SIGBUS, handleSignalException);
    signal(SIGPIPE, handleSignalException);
    signal(SIGHUP, handleSignalException);
    signal(SIGINT, handleSignalException);
    signal(SIGQUIT, handleSignalException);
    signal(SIGABRT, handleSignalException);
    signal(SIGILL, handleSignalException);
    signal(SIGTRAP, handleSignalException);
    
}

void handleSignalException(int signal0) {
    
//    NSMutableString *crashString = [[NSMutableString alloc]init];
//    void* callstack[128];
//    int i, frames = backtrace(callstack, 128);
//    char** traceChar = backtrace_symbols(callstack, frames);
//    for (i = 0; i <frames; ++i) {
//        [crashString appendFormat:@"%s\n", traceChar[i]];
//    }
//    NSArray<NSString *> * callStacks = [crashString componentsSeparatedByString:@"\n"];
    
    NSArray<NSString *> * callStacks = [NSThread callStackSymbols];
    
    NSMutableDictionary *userInfo = [NSMutableDictionary dictionaryWithObject:[NSNumber numberWithInt:signal0] forKey:UncaughtExceptionHandlerSignalKey];
    [userInfo setObject:callStacks forKey:UncaughtExceptionHandlerAddressesKey];
    [userInfo setObject:@"SigCrash" forKey:UncaughtExceptionHandlerFileKey];
    
    NSString *reason = [NSString stringWithFormat:@"Signal %d was raised.\n", signal0];
    NSException *exception = [NSException exceptionWithName:UncaughtExceptionHandlerSignalExceptionName reason:reason userInfo:userInfo];
    
    [[DWLogManager sharedManager] performSelectorOnMainThread:@selector(handleException:)
                                                              withObject:exception
                                                           waitUntilDone:YES];
    
    signal(SIGSEGV, SIG_DFL);
    signal(SIGFPE, SIG_DFL);
    signal(SIGBUS, SIG_DFL);
    signal(SIGPIPE, SIG_DFL);
    signal(SIGHUP, SIG_DFL);
    signal(SIGINT, SIG_DFL);
    signal(SIGQUIT, SIG_DFL);
    signal(SIGABRT, SIG_DFL);
    signal(SIGILL, SIG_DFL);
    signal(SIGTRAP, SIG_DFL);
    
}

- (void)registerUncaughtExceptionHandler {
    // 将别人之前注册的Crash回调取出并备份
    previousUncaughtExceptionHandler = NSGetUncaughtExceptionHandler();
    // 然后再注册自己的
    NSSetUncaughtExceptionHandler(&UncaughtExceptionHandler);
}

// 崩溃时的回调函数
static void UncaughtExceptionHandler(NSException * exception) {
    // 保存Crash日志到沙盒cache目录
    [[DWLogManager sharedManager] writeCrashLog:exception callStackSymbols:exception.callStackSymbols];
    // 在自己handler处理完后记得把别人的handler注册回去，形成规范的SOP
    if (previousUncaughtExceptionHandler) {
        previousUncaughtExceptionHandler(exception);
    }
    // 杀掉程序，这样可以防止同时抛出的SIGABRT被Signal异常捕获
    kill(getpid(), SIGKILL);
}

- (void)writeCrashLog:(NSException *)exception callStackSymbols:(NSArray <NSString *>*)callStackSymbols {
    
    NSString *reason = [exception reason];
    NSString *name = [exception name];
    
    NSDictionary *infoDictionary = [[NSBundle mainBundle] infoDictionary];
    NSString *appVersion = [infoDictionary objectForKey:@"CFBundleShortVersionString"];
    NSString *appBuild = [infoDictionary objectForKey:@"CFBundleVersion"];
    NSString* systemVersion = [[UIDevice currentDevice] systemVersion];
    
    DWCrashLogModel *crashLog = [DWCrashLogModel new];
    crashLog.appVer = appVersion;
    crashLog.buildVer = appBuild;
    crashLog.systemVer = systemVersion;
    crashLog.name = name;
    crashLog.reason = reason;
    crashLog.message = callStackSymbols;
    
    NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
    formatter.dateFormat = @"yyyy-MM-dd_HH:mm:ss.SSS";
    crashLog.crateDate = [formatter stringFromDate:[NSDate date]];
    
    NSString *filename = [NSString stringWithFormat:@"%@.log", crashLog.crateDate];
    NSString *filepath = [[DWLogManager getCrashPath] stringByAppendingPathComponent:filename];
    [FCFileManager createFileAtPath:filepath withContent:[crashLog description]];
}

- (void)startDDLog {
    
    [DDLog addLogger:[DDOSLogger sharedInstance]]; // Uses os_log
    [[DDOSLogger sharedInstance] setLogFormatter:[[DWLogFileForrmatter alloc] init]];

    DDFileLogger *fileLogger = [[DDFileLogger alloc] init]; // File Logger
    fileLogger.rollingFrequency = 60 * 60 * 24; // 24 hour rolling
    fileLogger.logFileManager.maximumNumberOfLogFiles = 7;
    fileLogger.logFormatter = [[DWLogFileForrmatter alloc] init];
    [DDLog addLogger:fileLogger];
    
}

- (void)log:(NSString *)string {
    
    if (!openLog) {
        return;
    }
    
    DDLogVerbose(@"%@", string);
}

+ (NSString *)getCrashPath {
    NSString *path = @"Crash/";
    path = [[FCFileManager pathForCachesDirectory] stringByAppendingPathComponent:path];
    [FCFileManager createDirectoriesForPath:path];
    return path;
}

+ (NSString *)getLogPath {
    NSString *path = @"Logs/";
    path = [[FCFileManager pathForCachesDirectory] stringByAppendingPathComponent:path];
    [FCFileManager createDirectoriesForPath:path];
    return path;
}

@end


/// 崩溃模型
@implementation DWCrashLogModel

- (NSString *)description {
    NSMutableString *ms = [NSMutableString new];
    for (NSString *str in _message) {
        [ms appendFormat:@"\n%@；", str];
    }
    return [NSString stringWithFormat:
            @"crateDate: %@ \nappVer: %@\nbuildVer: %@ \nsystemVer: %@ \nname: %@ \nreason: %@ \nmessage: %@ \n",
            _crateDate,
            _appVer,
            _buildVer,
            _systemVer,
            _name,
            _reason,
            ms.copy];
}

@end
